package com.alex.utils.search.yandex;

import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.entity.StringEntity;

import com.alex.utils.http.RequestBuilder;
import com.alex.utils.http.RequestBuilderFactory;
import com.alex.utils.search.SearchException;
import com.alex.utils.search.SearchResults;
import com.alex.utils.search.SearchResults.Document;
import com.alex.utils.search.Searcher;
import com.alex.utils.search.yandex.jaxb.request.Groupby;
import com.alex.utils.search.yandex.jaxb.request.Groupings;
import com.alex.utils.search.yandex.jaxb.request.ObjectFactory;
import com.alex.utils.search.yandex.jaxb.request.Request;
import com.alex.utils.search.yandex.jaxb.response.Yandexsearch;
import com.alex.utils.search.yandex.jaxb.response.Yandexsearch.Response.Error;
import com.alex.utils.search.yandex.jaxb.response.Yandexsearch.Response.Results.Grouping.Group;
import com.alex.utils.search.yandex.jaxb.response.Yandexsearch.Response.Results.Grouping.Group.Doc;
import com.alex.utils.search.yandex.jaxb.response.Yandexsearch.Response.Results.Grouping.Page;

public class YandexXMLSearcher extends Searcher {

	private static final String URL = "http://xmlsearch.yandex.ru/xmlsearch?user=%%USER%%&key=%%KEY%%";
	
	private static final int RESULTS_PER_PAGE = 100;

	private String url;

	private RequestBuilderFactory requestBuilderFactory;
	
	private int docsInGroup = 3;

	public YandexXMLSearcher(String user, String key) {
		this.url = StringUtils.replaceEach(URL, new String[] { "%%USER%%", "%%KEY%%" }, new String[] { user, key });
	}

	public void setRequestBuilderFactory(RequestBuilderFactory requestBuilderFactory) {
		this.requestBuilderFactory = requestBuilderFactory;
	}
	
	public SearchResults search(String query, int pageNumber) throws SearchException, IOException {
		try {
			Request request = new ObjectFactory().createRequest();
			Groupings groupings = new Groupings();
			Groupby groupby = new Groupby();

			request.setQuery(query);
			request.setGroupings(groupings);
			request.setPage(new BigInteger(String.valueOf(pageNumber)));

			groupings.getGroupby().add(groupby);
			groupby.setAttr("d");
			groupby.setMode("deep");
			groupby.setGroupsOnPage(new BigInteger(String.valueOf(RESULTS_PER_PAGE)));
			groupby.setDocsInGroup(new BigInteger(String.valueOf(docsInGroup)));

			JAXBContext jaxbContext = JAXBContext.newInstance(Request.class);
			StringWriter writer = new StringWriter();
			jaxbContext.createMarshaller().marshal(request, writer);

			RequestBuilder requestBuilder = requestBuilderFactory.post(url);
			requestBuilder.entity(new StringEntity(writer.toString(), "utf-8"));

			String response = requestBuilder.getResultAsString();

			FileUtils.write(new File("/tmp/response.xml"), response);

			jaxbContext = JAXBContext.newInstance(Yandexsearch.class);
			Yandexsearch yandexsearch = (Yandexsearch) jaxbContext.createUnmarshaller().unmarshal(new StringReader(response));
			
			if (!yandexsearch.getResponse().getError().isEmpty()) {
				Error error = yandexsearch.getResponse().getError().get(0);
				if ("15".equals(error.getCode())) { // No results were found
					List<Document> documents = Collections.emptyList();
					return new SearchResults(documents, false);
				} else {
					throw new SearchException(error.getValue());
				}
			}
			
			Page pageInfo = yandexsearch.getResponse().getResults().getGrouping().getPage();
			
			if (pageInfo.getValue() != pageNumber) {
				List<Document> documents = Collections.emptyList();
				return new SearchResults(documents, false);
			}

			Set<Document> set = new HashSet<Document>();
			List<Document> documents = new ArrayList<Document>();
			for (Group group : yandexsearch.getResponse().getResults().getGrouping().getGroup()) {
				for (Doc doc : group.getDoc()) {
					Document document = new Document(doc.getUrl());
					if (set.add(document)) {
						documents.add(document);
					}
				}
			}

			return new SearchResults(documents, pageInfo.getFirst() + RESULTS_PER_PAGE - 1 == pageInfo.getLast());
		} catch (JAXBException e) {
			throw new SearchException(e);
		}
	}
}
